﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Windows;

namespace CustomConsole
{
    public interface IConsoleHost : IDisposable
    {
        bool StartShell(string shell, string initDir, string consoleTitle, int startupRows, int startupCols);

        event ConsoleChangeCallback ConsoleChange;
        event ConsoleCloseCallback ConsoleClose;

        bool PostMessage(int Msg, int wParam, int lParam);
        bool KeyDown(int code);

        void SetColRow(int col, int row);

        CHAR_INFO[] GetChars();
        int Columns { get; }
        int Rows{ get; }

        int BufferRows{ get; }

        int BufferColumns{ get; }
        bool NeedVerticalScrollBar { get; }
        bool NeedHorizontalScrollBar {get;}
        int VerticalScrollPosition{ get; }

        int HorizontalScrollPosition{ get; }

        void SetScrollPosition(int col, int row);

        Point CursorPosition();

        string RegionString(int startCol, int startRow, int endCol, int endRow);
        void SendText(string text);

        DateTime LastChange { get; }
    }
    public class ConsoleHost : IConsoleHost
    {
        public DateTime LastChange { get; set; }
        NativeConsoleHost _nativeHost;
        ConsoleParams _paramCache;
        CHAR_INFO[] _infosCache;
        bool _cached = false;

        public ConsoleHost()
        {
            LastChange = DateTime.Now;
            _nativeHost = new NativeConsoleHost();            
        }

        private void EnsureCache()
        {
            if (!_cached)
            {
                // this two value must be the same time.
                _paramCache = _nativeHost.GetConsoleParams();
                _infosCache = _nativeHost.GetConsoleBuffer(_paramCache.dwRows * _paramCache.dwColumns);
                _cached = true;
            }
        }

        private ConsoleParams ParamCache
        {
            get
            {
                EnsureCache();
                return _paramCache;
            }
        }

        private CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo
        {
            get
            {
                return _nativeHost.GetConsoleInfo();
            }
        }

        public bool StartShell(string shell, string initDir, string consoleTitle, int startupRows, int startupCols)
        {
            int ret = _nativeHost.StartShell(shell, initDir, consoleTitle, startupRows, startupCols);
            if (ret != 0)
            {
                ret = _nativeHost.StartMonitor();
                return ret != 0;
            }
            return false;
        }

        public event ConsoleChangeCallback ConsoleChange
        {
            add
            {
                _nativeHost.ConsoleChange += value;
            }
            remove
            {
                _nativeHost.ConsoleChange -= value;
            }
        }

        public event ConsoleCloseCallback ConsoleClose
        {
            add
            {
                _nativeHost.ConsoleClose += value;
            }
            remove
            {
                _nativeHost.ConsoleClose -= value;
            }
        }

        public bool PostMessage(int Msg, int wParam, int lParam)
        {
            LastChange = DateTime.Now;
            return _nativeHost.PostMessage(ParamCache.hwndConsoleWindow, Msg, wParam, lParam);
        }

        private const int WM_KEYDOWN = 256;
        private const int WM_KEYUP = 257;

        public bool KeyDown(int code)
        {

            return PostMessage(WM_KEYDOWN, code, 0);
        }

        public void SetColRow(int col, int row)
        {
            InvalidateCash();
            _nativeHost.SetConsoleSize(col, row);
        }

        private void InvalidateCash()
        {
            _cached = false;
        }

        public CHAR_INFO[] GetChars()
        {
            InvalidateCash(); // to much, maybe.
            EnsureCache();
            return _infosCache;
        }

        public void Dispose()
        {
            if (_nativeHost != null)
            {
                _nativeHost.Dispose();
                _nativeHost = null;
            }
        }

        public string RegionString(int startCol, int startRow, int endCol, int endRow)
        {
            CHAR_INFO[] infos = GetChars();
            Debug.Assert(startCol <= endCol && startRow <= endRow);
            StringBuilder bldr = new StringBuilder();
            for (int row = startRow; row <= endRow; row++)
            {
                for (int col = startCol; col <= endCol; col++)
                {
                    CHAR_INFO info = infos[row * Columns + col];
                    if (info.Char.UnicodeChar != 0)
                    {
                        bldr.Append((char)info.Char.UnicodeChar);
                    }
                }
                if(row != endRow)
                    bldr.Append("\r\n");
            }
            return bldr.ToString();
        }

        public int Columns
        {
            get { return ParamCache.dwColumns; }
        }

        public int Rows
        {
            get { return ParamCache.dwRows; }
        }

        public int BufferRows
        {
            get { return ParamCache.dwBufferRows; }
        }

        public int BufferColumns
        {
            get { return ParamCache.dwBufferColumns; }
        }

        public bool NeedVerticalScrollBar
        {
            get
            {
                return BufferRows > Rows;
            }
        }

        public bool NeedHorizontalScrollBar
        {
            get
            {
                return BufferColumns > Columns;
            }
        }

        public int VerticalScrollPosition
        {
            get 
            {
                return ConsoleInfo.srWindow.Top;
            }
        }

        public int HorizontalScrollPosition
        {
            get
            {
                return ConsoleInfo.srWindow.Left;
            }
        }

        public void SetScrollPosition(int col, int row)
        {
            _nativeHost.SetScrollPosition(col, row);
        }
        public Point CursorPosition()
        {
            CONSOLE_SCREEN_BUFFER_INFO info = _nativeHost.GetConsoleInfo();
            Point r = new Point(info.dwCursorPosition.X - info.srWindow.Left,
                info.dwCursorPosition.Y - info.srWindow.Top);
            return r;

        }

        public void SendText(string text)
        {
            LastChange = DateTime.Now;
            _nativeHost.SendTextToConsole(text);
        }
    }
}
